<?php

    /**
     * Compiler
     *
     * This is the compiler of CRON tasks
     *
     * @package      Core
     * @subpackage   Cron
     *
     * @license      GNU Lesser General Public Licence see LICENCE-LGPL file or http://www.gnu.org/licenses/lgpl.html
     */
    class Core_Cron_Compiler
    {

	////////////////////////////////////////////////////////////////////////////
	//                             Static Methods                             //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Reads the ini files
         * It also calculates the options. It stores the result in a temporary file
         *
		 * @param		string         $section
		 * @param		boolean|array  $config
		 * @throws		Core_Exception
		 * @throws		Core_ArgumentException
		 * @throws		Core_ArgumentNullException
         * @return      array   an object which contains task values
         */
        public static function get($section = 'production', $config = array('allowModifications' => true))
        {
            if ($section === null) {
                throw new Core_ArgumentNullException('section is a null reference');
            }

            if (strlen(trim($section)) == 0) {
                throw new Core_ArgumentException('section is a zero-length string');
            }

            $compile       = false;
			$registry      = Zend_Registry::getInstance();
			$cachefile     = Core_IO_Path::combine(PROJECT_TEMP_PATH, 'compiled', '~crons~');
            $cachefiletime = @filemtime($cachefile);

            if (!Core_IO_File::exists($cachefile)) {
                $compile = true;
            } else {
                foreach ($registry['modules'] as $module) {
                    $file = Core_IO_Path::combine($module, 'cron.ini.php');

                    if (Core_IO_File::exists($file)) {
                        if (@filemtime($file) > $cachefiletime) {
                            $compile = true;
                            break;
                        }
                    }
                }
            }

            if ($compile) {
    			$tasks        = null;
                $leaves       = array();
                $taskArray    = array();
                $dependencies = array();

                foreach ($registry['modules'] as $module) {
                    $file = Core_IO_Path::combine($module, 'cron.ini.php');

                    if (Core_IO_File::exists($file)) {
                        if ($tasks instanceof Zend_Config) {
                            $tasks->merge(new Zend_Config_Ini($file, $section, $config));
                        } else {
                            $tasks = new Zend_Config_Ini($file, $section, $config);
                        }
                    }
                }

                foreach ($tasks as $name => $task) {
                    if (empty($name)) {
                        throw new Core_Exception('FATAL ERROR: CRON task must be named');
                    } else {
                        $taskArray[$name] = array('name' => $name);
                    }

                    if (empty($task->type)) {
                        throw new Core_Exception('FATAL ERROR: CRON task must implement a type');
                    } else {
                        $taskArray[$name]['type'] = $task->type;
                    }

                    if (empty($task->module)) {
                        throw new Core_Exception('FATAL ERROR: CRON task must implement a module');
                    } else {
                        $taskArray[$name]['module'] = $task->module;
                    }

                    if (empty($task->key)) {
                        throw new Core_Exception('FATAL ERROR: CRON task must implement a key');
                    } else {
                        $taskArray[$name]['key'] = $task->key;
                    }

                    if (empty($task->lifespan)) {
                        throw new Core_Exception('FATAL ERROR: CRON task must implement a lifespan');
                    } else {
                        $taskArray[$name]['lifespan'] = $task->lifespan;
                    }

                    if (empty($task->action)) {
                        throw new Core_Exception('FATAL ERROR: CRON task must implement an action');
                    } else {
                        $taskArray[$name]['action'] = $task->action;
                    }

                    if (!empty($task->parameters)) {
                        $taskArray[$name]['parameters'] = explode(',', $task->parameters);
                    } else {
                        $taskArray[$name]['parameters'] = array();
                    }

                    if (!empty($task->comment)) {
                        $taskArray[$name]['comment'] = $task->comment;
                    } else {
                        $taskArray[$name]['comment'] = 'No comment';
                    }

                    if (!empty($task->requires)) {
                        $taskArray[$name]['requires'] = explode(',', $task->requires);
                    } else {
                        $taskArray[$name]['requires'] = array();
                    }

                    $dependencies[$name] = $taskArray[$name]['requires'];

                    if (count($taskArray[$name]['requires']) == 0) {
                        $leaves[] = $name;
                    }
                }

                // Garbage collector
                unset($tasks);

                foreach ($taskArray as $name => $values) {
                    foreach ($values['requires'] as $required) {
                        if (!array_key_exists(trim($required), $taskArray)) {
                            throw new Core_Exception('FATAL ERROR: missing CRON dependency "'. $required .'"');
                        }
                    }
                }

                $graph = new Core_Cron_Sort($dependencies, true);
                $order = $graph->tsort();

                // Garbage collector
                unset($dependencies);

                $order = array_merge($leaves, $order);
                $order = array_unique($order);

                // Garbage collector
                unset($graph);
                unset($leaves);

                $tasks = array();

                // Sort real tasks
                foreach ($order as $name) {
                    $tasks[] = $taskArray[$name];
                }

                // Garbage collector
                unset($order);
                unset($taskArray);

                Core_IO_File::writeAll($cachefile, var_export($tasks, true));
            } else {
                $str   = Core_IO_File::readAll($cachefile);
                $tasks = eval('return '. $str .';');
            }

            return $tasks;
        }
    }
